package gofire

import (
	"encoding/json"
	"errors"
	"fmt"
	"gofire/binding"
	"gofire/render"
	"io"
	"mime/multipart"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
)

const defaultMultipartMemory = 32 << 20

type Context struct {
	// request 是结构体，用指针可以节省内存，Writer 是一个接口类型，不能用指针
	Request        *http.Request
	w              http.ResponseWriter
	formCache      url.Values
	sameSite       http.SameSite
	server         *HttpServer
	RespStatusCode int
	// RespData []byte
	RespData []byte
}

func (c *Context) Method() string {
	return c.Request.Method
}

func (c *Context) Path() string {
	return c.Request.URL.Path
}

func (c *Context) GetHeader(key string) string {
	return c.Request.Header.Get(key)
}

func (c *Context) SetHeader(key, value string) {
	c.w.Header().Set(key, value)
}

func (c *Context) Query(key string) (value string) {
	value, _ = c.GetQuery(key)
	return
}

func (c *Context) GetQuery(key string) (string, bool) {
	if values, ok := c.GetQueryArray(key); ok {
		return values[0], ok
	}
	return "", false
}

func (c *Context) DefaultQuery(key, defaultValue string) string {
	if value, ok := c.GetQuery(key); ok {
		return value
	}
	return defaultValue
}

func (c *Context) QueryArray(key string) []string {
	value, _ := c.GetQueryArray(key)
	return value
}

func (c *Context) Cookie(name string) (string, error) {
	cookie, err := c.Request.Cookie(name)
	if err != nil {
		return "", err
	}
	return url.QueryUnescape(cookie.Value)
}

func (c *Context) SetSameSite(sameSite http.SameSite) {
	c.sameSite = sameSite
}

func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
	if path == "" {
		path = "/"
	}
	http.SetCookie(c.w, &http.Cookie{
		Name:     name,
		Value:    url.QueryEscape(value),
		MaxAge:   maxAge,
		Path:     path,
		Domain:   domain,
		SameSite: c.sameSite,
		Secure:   secure,
		HttpOnly: httpOnly,
	})
}

func (c *Context) GetQueryArray(key string) ([]string, bool) {
	query := c.Request.URL.Query()
	val, ok := query[key]
	return val, ok
	/*
		return c.URL.Query()[key]
		会报错，因为它试图返回两个值，但实际上只有一个值被返回（一个[]string）
	*/
	// return c.URL.Query()[key]
}

func (c *Context) PostForm(key string) string {
	value, _ := c.GetPostForm(key)
	return value
}

func (c *Context) GetPostForm(key string) (string, bool) {
	value, ok := c.GetPostFormArray(key)
	if ok {
		return value[0], ok
	}
	return "", false
}

func (c *Context) PostFormArray(key string) []string {
	value, _ := c.GetPostFormArray(key)
	return value
}

func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
	c.initPostForm()
	values, ok = c.formCache[key]
	return
}

func (c *Context) initPostForm() {
	if c.formCache == nil {
		c.formCache = make(url.Values)
		/*
			defaultMultipartMemory是传递给ParseMultipartForm的参数，表示在将文件切片存储到磁盘之前，允许最多使用多少内存。
			这个值通常设定为一定的字节数。具体的值取决于你的应用需求和系统资源，较大的值可能会使处理速度更快，但也会消耗更多的内存
		*/
		if err := c.Request.ParseMultipartForm(defaultMultipartMemory); err != nil {
			if !errors.Is(err, http.ErrNotMultipart) {
				fmt.Printf("error on parse multipart form array: %v", err)
			}
		}
		c.formCache = c.Request.PostForm
	}
}

func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
	return b.Bind(c.Request, obj)
}

func (c *Context) ShouldBindJSON(obj any) error {
	return c.ShouldBindWith(obj, binding.JSON)
}

func (c *Context) ShouldBindQuery(obj any) error {
	return c.ShouldBindWith(obj, binding.Query)
}

func (c *Context) JSON(code int, data interface{}) error {
	c.w.Header().Set("Content-Type", "application/json")
	c.w.WriteHeader(code)
	return json.NewEncoder(c.w).Encode(data)
}

func (c *Context) PlainText(code int, text string) error {
	c.w.Header().Set("Content-Type", "text/plain")
	c.w.WriteHeader(code)
	_, err := c.w.Write([]byte(text))
	return err
}

func (c *Context) HTML(code int, name string, data any) {
	_ = c.Render(code, c.server.GetRender(name, data))
}

func (c *Context) Redirect(code int, location string) {
	_ = c.Render(-1, render.Redirect{
		Code:     code,
		Location: location,
		Request:  c.Request,
	})
}

func (c *Context) ServerError(err error) {
	http.Error(c.w, "Internal HttpServer Error: "+err.Error(), http.StatusInternalServerError)
}

func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
	if c.Request.MultipartForm == nil {
		if err := c.Request.ParseMultipartForm(defaultMultipartMemory); err != nil {
			return nil, err
		}
	}

	f, fh, err := c.Request.FormFile(name)
	if err != nil {
		return nil, err
	}

	err = f.Close()
	return fh, err
}

func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dest string) error {
	src, err := file.Open()
	if err != nil {
		return fmt.Errorf("unable to open source file: %v", err)
	}
	defer src.Close()

	dir := filepath.Dir(dest)
	err = os.MkdirAll(dir, 0750)
	if err != nil {
		return fmt.Errorf("unable to create directory %s: %v", dir, err)
	}

	out, err := os.Create(dest)
	if err != nil {
		return fmt.Errorf("unable to create destination file %s: %v", dest, err)
	}
	defer func(out *os.File) {
		closeErr := out.Close()
		if err == nil && closeErr != nil {
			err = fmt.Errorf("error closing destination file: %v", closeErr)
		}
	}(out)

	_, err = io.Copy(out, src)
	if err != nil {
		return fmt.Errorf("unable to write to destination file: %v", err)
	}

	return nil
}

func (c *Context) Status(code int) {
	if code > 0 && code != http.StatusOK {
		c.w.WriteHeader(code)
	}
}

func (c *Context) Render(code int, r render.Render) error {
	c.Status(code)
	return r.Render(c.w)
}

func (c *Context) RemoteIP() string {
	ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
	if err != nil {
		return ""
	}
	ipObj := net.ParseIP(ip)
	if ipObj == nil {
		return ""
	}
	return ipObj.String()
}

func (c *Context) ClientIP() string {
	for _, header := range []string{"X-Real-IP", "X-Forwarded-For"} {
		headValue := c.GetHeader(header)
		ip, valid := c.validateIPHeader(headValue)
		if valid {
			return ip
		}
	}
	return c.RemoteIP()
}

func (c *Context) validateIPHeader(val string) (string, bool) {
	if val == "" {
		return "", false
	}
	// https://blog.csdn.net/m0_47404181/article/details/107143156
	items := strings.Split(val, ",")
	for i := len(items) - 1; i >= 0; i++ {
		ipStr := strings.TrimSpace(items[i])
		if net.ParseIP(ipStr) == nil {
			return "", false
		}

		if i == 0 {
			return ipStr, true
		}
	}

	return "", false
}
